﻿//  Add Wldap32.lib to the project
#include "ConsoleApplication1.h";

/*
	下面的代码示例演示如何使用 LDAP 更改通知控件接收对活动目录域服务中对象的更改通知。示例注册通知，读取对象的初始状态，然后使用循环等待并处理对对象的更改。

	首先，代码示例调用ldap_search_ext函数，这是一个异步搜索操作，在注册通知请求后返回。其次，在设置通知请求后，该示例调用 ldap_search_s函数，这是一个同步搜索操作，用于读取对象的当前状态。第三，该示例使用调用ldap_result等待异步搜索操作的结果的循环。当ldap_result返回时，示例处理搜索结果并重复循环。

	请注意，如果示例读取当前状态，然后设置通知请求，则在注册通知请求之前，会有一段时间可能发生更改。通过设置通知请求后读取对象，窗口将反向工作 - 您可以在读取初始状态之前收到更改通知。若要处理此内容，该示例在读取对象的初始状态时缓存对象uSNChanged属性值。然后，当ldap_result更改通知时，该示例将缓存的uSNChanged属性值与 ldap_result 。如果新的uSNChanged属性值小于或等于缓存值，则示例将丢弃结果，因为它们指示在初始读取操作之前发生的更改。

	此示例执行监视单个对象的基本级别搜索。您可以指定LDAP_SCOPE_ONELEVEL范围来监视指定对象的所有子对象。您还可以修改代码以注册最多五个通知请求，然后使用 ldap_result函数等待来自任何请求的通知。请记住，更改通知请求会影响服务器的性能。有关如何提高性能的信息，请参阅活动目录域服务中的更改通知。
*/

// Forward declarations
//VOID BuildGUIDString(WCHAR* szGUID, LPBYTE pGUID);
//BOOL ProcessResult(LDAP* pldapConnection, LDAPMessage* message, __int64* piUSNChanged);

//  GetChangeNotifications Function
//
//  Description:
//   The function binds to an LDAP server,
//   registers for change notifications,
//   retrieves the current state, and
//   processes change notifications.
//
//  Input Parameters:
//   szSearchBaseDN  The distinguished name of the object monitored.
//
//  Return Values
//   0   The function was successful.
//   1   An error occurred.
INT GetChangeNotifications(LPWSTR szSearchBaseDN)
{
	INT err = LDAP_OPERATIONS_ERROR; //  Return value for LDAP operations.
	INT iretVal = 1;                 //  Return value of this function.
	INT inumberOfChanges = 300;        //  The number of changes to monitor.
	BOOL bSuccess = FALSE;           //  Results from processing search results.
	ULONG version = LDAP_VERSION3;   //  LDAP version 3.
	LDAP* pldapConnection = NULL;    //  Pointer to the connection handle.
	LDAPControl simpleControl;       //  Structure used to represent both
									 //  client and server controls.
	PLDAPControl controlArray[2];    //  Pointer to the LDAPControl.
	LONG msgId;                      //  Used to identify the message.
	LDAPMessage* presults = NULL;    //  Pointer to LDAPMessage data structure
									 //  used to contain the search results.
	LDAPMessage* pmessage = NULL;    //  Pointer to an LDAPMessage data
									 //  structure used to contain each
									 //  entry of the search results.
	LPWSTR szAttribs[] = {           //  Array of attributes whose value will
									 //  be displayed when the attribute
									 //  changes to a different value.
						  {(LPWSTR)L"name"},
						  {(LPWSTR)L"isDeleted"},
						  {(LPWSTR)L"objectGUID"},
						  {(LPWSTR)L"uSNChanged"},
						  NULL
	};

	PWSTR hostName = (PWSTR)L"ldap.ad.shubuzuo.top"; // LDAP Server IP 地址 或者域名
	ULONG port = 389;                    // LDAP Server 监听的端口
	PWSTR bindDN = (PWSTR)L"cn=administrator, cn=users, dc=test, dc=shubuzuo, dc=top";
	PWSTR password = (PWSTR)L"1409458062@qq.com";

	__int64 iUSNChanged = 0; //  Stores the latest USNChanged value for the object.


	//  Connect to the default LDAP server. If successful, the function returns a handle to the
	//  connection.
	pldapConnection = ldap_open(hostName, //  Connect to the default LDAP server.
		port);   //  TCP port number.

//  If the connect function fails, show an error message and go to the error section.
	if (pldapConnection == NULL)
	{
		wprintf(L"ldap_open failed to connect. Error: 0x%x.\n", GetLastError());
		goto FatalExit0;
	}

	//  The function succeeded in opening a connection to an LDAP server.
	wprintf(L"Connected to server.\n");

	//  Set the options on connection blocks to specify LDAP version 3.
	pldapConnection->ld_lberoptions = 0;

	ldap_set_option(pldapConnection, LDAP_OPT_VERSION, &version);

	//  Asynchronously authenticate a client to the LDAP server using the current credentials.
	err = ldap_simple_bind_s(pldapConnection,      //  Connection handle
		bindDN,                 //  Null pointer
		password               //  Use the current credential.
		//LDAP_AUTH_SIMPLE
	); //  Use the most appropriate authentication method.

//  If the bind function fails, show an error message and go to the error section.
	if (LDAP_SUCCESS != err)
	{
		wprintf(L"Bind failed: 0x%x\n", err);
		goto FatalExit0;
	}

	//  The function succeeded in binding to the default LDAP server.
	wprintf(L"Successful bind.\n");

	//  Set up the control to search the directory for objects that have
	//  changed from a previous state.
	simpleControl.ldctl_oid = (PWSTR)LDAP_SERVER_NOTIFICATION_OID_W;
	simpleControl.ldctl_iscritical = TRUE;

	//  Set up the control to specify a BER-encoded sequence of parameters to the
	//  length of the binary data and to initialize a pointer.
	simpleControl.ldctl_value.bv_len = 0;
	simpleControl.ldctl_value.bv_val = NULL;

	//  Initialize the control array values.
	controlArray[0] = &simpleControl;
	controlArray[1] = NULL;

	//  Start a persistent asynchronous search.
	// 这是一个异步搜索操作，在注册通知请求后返回。
	err = ldap_search_ext(pldapConnection,               //  Connection handle.
		(PWCHAR)szSearchBaseDN,        //  Distinguished name of the object to monitor.
		LDAP_SCOPE_SUBTREE,               //  Monitor the specified object.
		(PWSTR)L"ObjectClass=*",              //  The search filter.
		szAttribs,                     //  Attributes to retrieve.
		0,                             //  Retrieve attributes and values.
		(PLDAPControl*)&controlArray, //  Server control.
		NULL,                          //  Client controls.
		0,                             //  No timeout.
		0,                             //  No size limit.
		(PULONG)&msgId);               //  Receives identifier for results.

//  If the search function fails, show an error message and go to the error section.
	if (LDAP_SUCCESS != err)
	{
		wprintf(L" The asynch search failed. Error: 0x%x \n", err);
		goto FatalExit0;
	}

	//  The asynchronous search function succeeded.
	wprintf(L"Registered for change notifications on %s.\n", szSearchBaseDN);
	wprintf(L"Message identifier is %d.\n", msgId);

	//  After starting the persistent search, perform a synchronous search
	//  to retrieve the current state of the monitored object.
	err = ldap_search_s(pldapConnection,        //  Connection handle.
		(PWCHAR)szSearchBaseDN, //  Distinguished name of the object to monitor.
		LDAP_SCOPE_SUBTREE,        //  Monitor the specified object.
		(PWSTR)L"ObjectClass=*",       //  The search filter.
		szAttribs,              //  List of attributes to retrieve.
		0,                      //  Retrieve attributes and values.
		&presults);             //  Receives the search results.

//  If the synchronous search function fails, show an error message and go to
//  the error section.
	if (LDAP_SUCCESS != err)
	{
		wprintf(L"ldap_search_s error: 0x%x\n", err);
		goto FatalExit0;
	}

	//  The synchronous search function succeeded.
	wprintf(L"\nGot current state\n");

	//  Process the search results by retrieving the first entry of a message
	//  and calling the function that processes the message. As long as the
	//  message contains an entry that is not null, retrieve and process the
	//  the next entry.
	pmessage = ldap_first_entry(pldapConnection, presults);

	while (pmessage != NULL)
	{
		bSuccess = ProcessResult(pldapConnection, pmessage, &iUSNChanged);
		pmessage = ldap_next_entry(pldapConnection, pmessage);
	}

	//  After processing the message, frees the results of the synchronous search routine.
	ldap_msgfree(presults);
	presults = NULL;

	//  Begin checking for changes on the specified object.
	wprintf(L"Waiting for change notifications...\n");

	//  This loop monitors for changes on the specified object. The number of
	//  returned changes is specified.
	for (INT icounter = 0;            //  Lower limit.
		icounter < inumberOfChanges; //  Upper limit.
		icounter++)                  //  Increment the counter after each loop.
	{
		//  Wait for the results of the asynchronous search.
		presults = NULL;
		err = ldap_result(pldapConnection, //  Connection handle.
			LDAP_RES_ANY,    //  Message identifier.
			LDAP_MSG_ONE,    //  Retrieve one message at a time.
			NULL,            //  No timeout.
			&presults);      //  Receives the search results.

		if ((err == (ULONG)-1) || (presults) == NULL)
		{
			wprintf(L"ldap_result error: 0x%x\n", pldapConnection->ld_errno);
			break;
		}

		wprintf(L"\nGot a notification. Message ID: %d\n", presults->lm_msgid);

		//  Process the search results by retrieving the first entry of a message
		//  and calling the function that processes the message. As long as the
		//  message contains an entry that is not null, retrieve and process the
		//  the next entry.
		pmessage = ldap_first_entry(pldapConnection, presults);

		while (pmessage != NULL)
		{
			bSuccess = ProcessResult(pldapConnection, pmessage, &iUSNChanged);
			pmessage = ldap_next_entry(pldapConnection, pmessage);
		}

		// Free the resources used by the results.
		ldap_msgfree(presults);

		// Set the pointer to null.
		presults = NULL;
	}

	iretVal = 0;
	wprintf(L"The program returned the last %d changes.\n", inumberOfChanges);

	//  Error Section.
FatalExit0:

	//  If a connection succeeds, frees resources associated with an LDAP session.
	if (pldapConnection)
		ldap_unbind(pldapConnection);

	//  Free resources used by the results.
	if (presults)
		ldap_msgfree(presults);

	return iretVal;
}

// BuildGUIDString
//
// Description:
// The function turns the GUID into a string.
//
// Input parameters:
//  szGUID  Pointer to a null-terminated string that will contain the
//          the string version of the GUID.
//  pGUID   Pointer to the GUID to be converted to a string.
VOID BuildGUIDString(WCHAR* szGUID, LPBYTE pGUID)
{
	DWORD i = 0;
	DWORD dwlen = sizeof(GUID);
	WCHAR buf[4];
	wcscpy_s(szGUID, dwlen, L"");
	for (i; i < dwlen; i++)
	{
		swprintf_s(buf, L"%02x", pGUID[i]);
		wcscat_s(szGUID, dwlen, buf);
	}
}

//  ProcessResult
//
//  Description:
//  This function processes and displays the search results.
//
//  Input Parameters:
//   pldapConnection     Pointer to the connection handle.
//   pmessage            Pointer to the result entry to process.
//   piUSNChanged        Pointer to the latest USNChanged value.
//
//  Return Values:
//   True    The function succeeded.
//   False   The function failed.
BOOL ProcessResult(LDAP* pldapConnection,
	LDAPMessage* pmessage,
	__int64* piUSNChanged)
{

	PWCHAR* value = NULL;
	__int64 iNewUSNChanged;
	PWCHAR dn = NULL, attribute = NULL;
	BerElement* opaque = NULL;
	berval** pbvGUID = NULL;
	WCHAR szGUID[40]; //  String version of the objectGUID attribute.
	ULONG count, total;

	//  Get the uSNChanged attribute to determine if this result is new data.
	value = ldap_get_values(pldapConnection, pmessage, (PWSTR)L"uSNChanged");

	//  The attempt to get the attribute value failed so the function fails.
	if (!value)
	{
		wprintf(L"ldap_get_values error\n");
		return FALSE;
	}

	//  Convert the attribute value from a string to an integer.
	iNewUSNChanged = _wtoi64(value[0]);

	//  If this attribute value is less than or equal to the previous value, the result
	//  contains out-of-date data. Therefore, discard the attribute value.
	if (iNewUSNChanged <= *piUSNChanged)
	{
		wprintf(L"Discarding outdated search results.\n");
		ldap_value_free(value);

		return TRUE;
	}
	else
	{
		*piUSNChanged = iNewUSNChanged;
		ldap_value_free(value);
	}

	//  The search results are newer than the previous state; process
	//  the results.
	dn = ldap_get_dn(pldapConnection, pmessage);

	//  If the function does not return a distinguished name, then an
	//  error occurred.
	if (!dn)
	{
		wprintf(L"ldap_get_dn error\n");
		return FALSE;
	}

	//  Display the distinguished name of the object.
	wprintf(L"    Distinguished Name is : %s\n", dn);

	//  Free the resources used to get the distinguished name.
	ldap_memfree(dn);

	//  Display the new values of the specified attributes
	attribute = ldap_first_attribute(pldapConnection, pmessage, &opaque);

	while (attribute != NULL)
	{
		//  Handle objectGUID as a binary value.
		if (_wcsicmp(L"objectGUID", attribute) == 0)
		{
			wprintf(L"    %s: ", attribute);
			pbvGUID = ldap_get_values_len(pldapConnection, pmessage, attribute);
			if (pbvGUID)
			{
				BuildGUIDString(szGUID, (LPBYTE)pbvGUID[0]->bv_val);
				wprintf(L"%s\n", szGUID);
			}
			ldap_value_free_len(pbvGUID);
		}
		else
		{
			//  Handle other attributes as string values.
			value = ldap_get_values(pldapConnection, pmessage, attribute);
			wprintf(L"    %s: ", attribute);
			if (total = ldap_count_values(value) > 1)
			{
				for (count = 0; count < total; count++)
					wprintf(L"        %s\n", value[count]);
			}
			else
			{
				wprintf(L"%s\n", value[0]);
				ldap_value_free(value);
			}
		}
		ldap_memfree(attribute);
		attribute = ldap_next_attribute(pldapConnection, pmessage, opaque);
	}

	return TRUE;
}

//  Entry point for the application.
int wmain()
{
	PWCHAR szSearchBaseDN = NULL;
	wprintf(L"\n");
	/*if (cArgs < 2)
	{
		wprintf(L"Missing the distinguished name of the search base.\n");
		wprintf(L"Usage: getchanges <distinguished name of search base>\n");
		return 0;
	}*/
	//szSearchBaseDN = (PWCHAR)pArgs[1];
	szSearchBaseDN = (PWCHAR)L"cn=Users,dc=test,dc=shubuzuo,dc=top";
	setlocale(LC_CTYPE, "");
	wprintf(L"%s\n", L"好家伙");
	return GetChangeNotifications(szSearchBaseDN);
}
